package hu.vissy.yacap;

import hu.vissy.yacap.exception.CommandLineParserException;
import hu.vissy.yacap.option.CommandLineOption;

import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.Map;

public class OptionSet {

    private static final String DEFAULT_UNNAMED_OPTION_VALUE_NAME = "value";

    //private static final String UNNAMED_OPTION_LONG_FORM = "#UNNAMED";

    /**
     * Multiplicity of the unnamed option.
     */
    private EnumSet<UnnamedOptionMultiplicity> unnamedMultiplicity = UnnamedOptionMultiplicity.NONE;

    private String unnamedValueName = DEFAULT_UNNAMED_OPTION_VALUE_NAME;

    private String unnamedDescription = "";

    private String descriptionPrologue;

    private String descriptionEpilogue;

    private Map<String, CommandLineOption<?>> options = new LinkedHashMap<String, CommandLineOption<?>>();

    /**
     * Constructor.
     */
    public OptionSet() {
    }

    /**
     * @param unnamedMultiplicity
     *            multiplicity rules for unnamed options.
     */
    public void setUnnamedMultiplicity(EnumSet<UnnamedOptionMultiplicity> unnamedMultiplicity) {
        this.unnamedMultiplicity = unnamedMultiplicity;
    }

    /**
     * @param unnamedValueName
     *            The label (name) of the unnamed options used in command line
     *            help.
     */
    public void setUnnamedValueName(String unnamedValueName) {
        this.unnamedValueName = unnamedValueName;
    }

    /**
     * @param unnamedDescription
     *            The description of the unnamed options used in command line
     *            help.
     */
    public void setUnnamedDescription(String unnamedDescription) {
        this.unnamedDescription = unnamedDescription;
    }

    /**
     * @param descriptionPrologue
     *            The description prologue displayed before the detailed option
     *            descriptions.
     */
    public void setDescriptionPrologue(String descriptionPrologue) {
        this.descriptionPrologue = descriptionPrologue;
    }

    /**
     * @param descriptionEpilogue
     *            The description epilogue displayed after the detailed option
     *            descriptions.
     */
    public void setDescriptionEpilogue(String descriptionEpilogue) {
        this.descriptionEpilogue = descriptionEpilogue;
    }


    /**
     * Registers a new option.
     *
     * @param option
     *            The new option to be added to the set.
     * @throws CommandLineParserException
     *             If there is an already registered option with the same short
     *             or long form.
     */
    public void registerOption(CommandLineOption<?> option) {
        // Checks for duplicates
        if (option.getShortForm() != CommandLineOption.NO_SHORT_FORM) {
            CommandLineOption<?> oo = findByShortForm(option.getShortForm());
            if (oo != null) {
                throw new CommandLineParserException("Duplicated short form: " + option.getShortForm() + " by options: " + oo.getLongForm() + ", "
                        + option.getLongForm());
            }
        }
        if (options.containsKey(option.getLongForm())) {
            throw new CommandLineParserException("Duplicated long form: " + option.getLongForm());
        }

        options.put(option.getLongForm(), option);
    }

    /**
     * Finds a registered option by its short form. Note that searching for
     * {@value CommandLineOption#NO_SHORT_FORM} will always return with null.
     *
     * @param shortForm
     *            The short form character to search for.
     * @return The option definition with the given short form or null, if no
     *         option found.
     */
    public CommandLineOption<?> findByShortForm(char shortForm) {
        if (shortForm != CommandLineOption.NO_SHORT_FORM) {
            for (CommandLineOption<?> o : options.values()) {
                if (o.getShortForm() == shortForm) {
                    return o;
                }
            }
        }
        return null;
    }

    /**
     * Finds a registered option by its long form.
     *
     * @param longForm
     *            The long form character to search for.
     * @return The option definition with the given long form or null, if no
     *         option found.
     */
    public CommandLineOption<?> findByLongForm(String longForm) {
        return options.get(longForm);
    }

    /**
     * Finds a registered option by its value key.
     *
     * @param valueKey
     *            The value key to search for.
     * @return The option definition with the given valueKey or null, if no
     *         option found.
     */
    public CommandLineOption<?> findByValueKey(String valueKey) {
        for (CommandLineOption<?> o : options.values()) {
            if (o.getValueKey() == valueKey) {
                return o;
            }
        }
        return null;
    }


    /**
     * @return the unnamedMultiplicity
     */
    public EnumSet<UnnamedOptionMultiplicity> getUnnamedMultiplicity() {
        return unnamedMultiplicity;
    }

    /**
     * @return the unnamedValueName
     */
    public String getUnnamedValueName() {
        return unnamedValueName;
    }

    /**
     * @return the unnamedDescription
     */
    public String getUnnamedDescription() {
        return unnamedDescription;
    }

    /**
     * @return the descriptionPrologue
     */
    public String getDescriptionPrologue() {
        return descriptionPrologue;
    }

    /**
     * @return the descriptionEpilogue
     */
    public String getDescriptionEpilogue() {
        return descriptionEpilogue;
    }


    public Collection<CommandLineOption<?>> getOptions() {
        return Collections.unmodifiableCollection(options.values());
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("OptionSet[");
        for (CommandLineOption<?> o : options.values()) {
            sb.append(o).append(", ");
        }
        sb.append(']');
        return sb.toString();
    }



}
